package network;

import da.BuildingDA;
import da.ResourceDA;
import da.UserDA;
import da.VillageDA;
import data.Building;
import data.Resource;
import data.User;
import data.Village;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Timer;

/**
 * Class MaiaServer.java
 * 
 * Contains the core implementation of the server:
 * <ul>
 *  <li>Waits for incomming connection requests</li>
 *  <li>Assigns a client handler for each of the requests.</li>
 * <ul>
 * 
 * The server is also responsible for:
 * <ul>
 *  <li>Communication between clients.<li>
 *  <li>Multicasting to all clients.</li>
 * </ul>
 * 
 * @author groep 11
 * @date 04/03/2008
 */
public class MaiaServer implements Runnable {

    private static ServerSocket servSocket;
    private final int PORT = 2008;
    private final int INTERVAL = 60000;
    private static MaiaServer instance = new MaiaServer();
    private ExecutorService handlerPool;
    private Vector<ClientHandler> clienthandlers;
    private Vector<ClientHandler> worldClienthandlers;
    private Timer timer;

    private MaiaServer() {
        clienthandlers = new Vector<ClientHandler>();
        worldClienthandlers = new Vector<ClientHandler>();
    }

    /**
     * @return singleton object server
     */
    public static MaiaServer getInstance() {
        return instance;
    }

    /**
     * List of all active ClientHandlers.
     * @return clienthandlers
     */
    public Vector<ClientHandler> getClienthandlers() {
        return this.clienthandlers;
    }

    /**
     * Set active ClientHandlers
     * @param clienthandlers
     */
    public void setClientHandlers(Vector<ClientHandler> clienthandlers) {
        this.clienthandlers = clienthandlers;
    }

    /**
     * List of all clienthandlers participating in the Maia world communication.
     * @return
     */
    public Vector<ClientHandler> getWorldClientHandlers() {
        return this.worldClienthandlers;
    }

    /**
     * Set list of all clienthandlers particiapting in the Maia world communication.
     * @param worldClienthandlers
     */
    public void setWorldClientHandlers(Vector<ClientHandler> worldClienthandlers) {
        this.worldClienthandlers = worldClienthandlers;
    }

    public void addToClienthandlers(ClientHandler clh) {
        this.clienthandlers.add(clh);
        printNumberOfConnectedClients();
    }

    public void removeFromClienthandlers(ClientHandler clh) {
        this.clienthandlers.remove(clh);
        printNumberOfConnectedClients();
    }

    public void addToWorldClienthandlers(ClientHandler clh) {
        this.worldClienthandlers.add(clh);
        printNumberOfConnectedWorldClients();

        // Send updated status to all world clients
        updateOnlineWorldUsersStatus();

    }

    public void removeFromWorldClienthandlers(ClientHandler clh) {
        this.worldClienthandlers.remove(clh);
        printNumberOfConnectedWorldClients();

        // Send updated status to all world clients
        updateOnlineWorldUsersStatus();
    }

    public void run() {

        // Every 5sec server will update resources of all clients
        timer = new Timer(INTERVAL, new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                updateResources();
            }
            });
        timer.start();

        try {
            servSocket = new ServerSocket(PORT);

            handlerPool = Executors.newCachedThreadPool();
            do {
                startServer();
            } while (!servSocket.isClosed());

        } catch (IOException ex) {
            System.out.println("Server: Startup error!:" + ex.getMessage());
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        MaiaServer server = new MaiaServer();
        server.run();
    }

    /**
     * Provides a list with all the usernames of online users.
     * @return usernames
     */
    protected Vector<User> getOnlineWorldUsers() {
        Vector<User> onlineUsers = new Vector<User>();

        for (ClientHandler clh : worldClienthandlers) {
            if (clh.getUser() != null) {
                onlineUsers.add(clh.getUser());
            }
        }
        return onlineUsers;
    }

    protected boolean isAlreadyLoggedIn(String username) {

        for (ClientHandler clh : clienthandlers) {
            if (clh.getUser() != null) {
                if (clh.getUser().getUsername().equals(username)) {
                    return true;
                }
            }
        }

        return false;
    }

    // <editor-fold desc="STARTEN/STOPPEN SERVER" defaultstate="collapsed">
    /*
     * Starts the server.
     */
    private void startServer() {

        Socket link = null;

        System.out.println("Server: Opening socket");

        try {
            // wait for new client
            System.out.println("Server: Waiting for client...");
            link = servSocket.accept();
            System.out.println("Server: Connected");

            // create and start clienthandler
            int id = clienthandlers.size() + 1;
            ClientHandler clh = new ClientHandler(link, this, id);
            Thread clhThread = new Thread(clh);
            handlerPool.execute(clhThread);

            // add to active connections
            addToClienthandlers(clh);
            printNumberOfConnectedClients();

        } catch (java.io.IOException ex) {
            if (!servSocket.isClosed()) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Stops the server.
     */
    public void stopServer() {
        try {
            // shut down clienthandler pool
            if (handlerPool != null) {
                handlerPool.shutdown();
            }
            for (ClientHandler clh : clienthandlers) {
                clh.closeConnection();
            }

            if (!servSocket.isClosed()) {
                servSocket.close();
            }

            System.out.println("Server down.");

        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     * Removes a ClientHandler from the list of active ClientHandlers
     * @param clh
     */
    protected void removeFromActiveClientHandlers(ClientHandler clh) {
        getClienthandlers().remove(clh);

    }

    protected void updateOnlineWorldUsersStatus() {
        for (ClientHandler c : worldClienthandlers) {
            final ClientHandler clh = c;
            Thread t = new Thread(new Runnable() {

                public void run() {
                    clh.updateOnlineWorldUsersStatus();
                }
            });
            t.start();
        }

    }

    private void updateResources() {
        Vector<User> users = UserDA.getInstance().getAllUsers();

        for (User user : users) {
            Village village = VillageDA.getInstance().getVillageByUsername(user.getUsername());
            Vector<Resource> resources = ResourceDA.getInstance().getResourcesOfVillage(village.getVillageId());
            for (Resource resource : resources) {
                if (resource != null) {
                    Vector<Building> buildings = BuildingDA.getInstance().getBuildingsByVillageIdResourceTypeName(village.getVillageId(), resource.getResourceTypeName());
                    for (Building building : buildings) {
                        if (resource.getAmount() == -1) {
                            resource.setAmount(0);
                        }
                        resource.setAmount(resource.getAmount() + building.getCapacity());
                    }
                    ResourceDA.getInstance().editResource(resource);
                }
            }
            for (ClientHandler client : worldClienthandlers) {
                if (client.getUser().getUserId() == user.getUserId()) {
                    try {
                        client.updateResources(resources);
                    } catch (IOException ex) {
                        Logger.getLogger(MaiaServer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
    }

    // </editor-fold>
    // <editor-fold desc="CHATTEN" defaultstate="collapsed">
    /**
     * Sends out a chat message to all friends
     * 
     * @param message
     * @param sender
     * @throws java.io.IOException
     */
    protected void sendFriendsChatMessage(String message, String sender) throws IOException {
        Vector<User> friends = UserDA.getInstance().getFriendsOfUser(sender);

        for (ClientHandler clh : clienthandlers) {
            for (User vriend : friends) {
                // send the message to any friend
                if (clh.getUser().getUserId() == vriend.getUserId()) {
                    clh.receiveFriendsChatMessage(message);
                }
            }

            // send the message to yourself
            if (clh.getUser().getUsername().equals(sender)) {
                clh.receiveFriendsChatMessage(message);
            }

        }
    }

    /**
     * Sends out a chat message to all users of a mutual tribe.
     * 
     * @param message
     * @param tribeId
     * @throws java.io.IOException
     */
    protected void sendTribeChatMessage(String message, int tribeId) throws IOException {
        for (ClientHandler clh : clienthandlers) {
            if (clh.getUser().getTribeId() == tribeId) {
                clh.receiveTribeChatMessage(message);
            }
        }
    }

    /**
     * Sends out a chat message to all online users
     * 
     * @param message
     * @throws java.io.IOException
     */
    protected void sendAllChatMessage(String message) throws IOException {
        for (ClientHandler clh : clienthandlers) {
            clh.receiveAllChatMessage(message);
        }
    }

    // </editor-fold>
    // <editor-fold desc="DEBUGGING METHODES (extra)" defaultstate="collapsed">
    protected void printNumberOfConnectedClients() {
        System.out.println("Number of connected clients: " + clienthandlers.size()); //DEBUG
    }

    protected void printNumberOfConnectedWorldClients() {
        System.out.println("Number of MaiaWorld clients: " + worldClienthandlers.size()); //DEBUG
    }
    // </editor-fold>
}
